Charts Built with Third-Party Libraries
When building custom visualizations using third-party libraries like D3.js within Muze Studio, you need to manually integrate with Muze's export system to enable PNG, PDF, and XLSX downloads. This tutorial shows you how to properly configure your custom charts for seamless exporting.
PNG/PDF Export Integration
When creating charts with libraries like D3.js, you need to notify Muze when rendering (including animations) is complete so exports capture the full visualization.
Key Concepts
- Disable Auto-Emit: Set
autoEmitRenderCompletedEvent: falseto take manual control - Check Print Mode: Use
env.isPrintModeto detect export/print operations - Emit Completion Event: Call
events.emitRenderCompletedEvent()when rendering is done
When using non-Muze visualizations in the Muze Studio environment, you must call events.emitRenderCompletedEvent() after your chart finishes rendering. This notifies the application that rendering is complete and is essential for proper visualisation lifecycle management.
Complete Example
/**
* Available Columns:
* "Ship Mode"
* "Total Discount"
* "Measure names" // If 'measureValues' is enabled.
* "Measure values" // If 'measureValues' is enabled.
* --- END ---
*/
/**
* Available Columns:
* "Ship Mode"
* "Total Discount"
*/
// Load D3.js library dynamically
const loadD3 = () => {
return new Promise((resolve, reject) => {
if (typeof d3 !== 'undefined') {
resolve(d3);
return;
}
const script = document.createElement('script');
script.src = 'https://d3js.org/d3.v7.min.js';
script.onload = () => resolve(window.d3);
script.onerror = reject;
document.head.appendChild(script);
});
};
// Wait for D3 to load, then create chart
loadD3().then(() => {
const {
setGlobalOptions,
events,
env,
getDataFromSearchQuery
} = viz;
setGlobalOptions({
autoEmitRenderCompletedEvent: false
});
const data = getDataFromSearchQuery();
const rawData = data.getData().data;
// Transform to D3 format: array of {name, value}
const data1 = rawData.map(row => ({
name: row["0"],
value: row["1"]
}));
const margin = { top: 20, right: 20, bottom: 30, left: 40 },
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
const svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const x = d3.scaleBand()
.domain(data1.map(d => d.name))
.range([0, width])
.padding(0.2);
const y = d3.scaleLinear()
.domain([0, d3.max(data1, d => d.value)])
.nice()
.range([height, 0]);
// Axes
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
// Bars with animation
const bars = svg.selectAll(".bar")
.data(data1)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => x(d.name))
.attr("y", y(0))
.attr("width", x.bandwidth())
.attr("height", 0)
.attr("fill", "steelblue");
if (!env.isPrintMode) {
bars
.transition()
.duration(800)
.attr("y", d => y(d.value))
.attr("height", d => height - y(d.value))
.on("end", function (_, i, nodes) {
if (i === nodes.length - 1) {
console.log("✅ Chart fully rendered with animation");
events.emitRenderCompletedEvent();
}
});
} else {
bars
.attr("y", d => y(d.value))
.attr("height", d => height - y(d.value));
events.emitRenderCompletedEvent();
}
}).catch(error => {
console.error("Failed to load D3.js:", error);
});
XLSX Export Integration
For XLSX exports, you need to provide custom logic to convert your third-party chart's data into a spreadsheet format.
Complete Example
/**
* Available Columns:
* "Ship Mode"
* "Total Discount"
*/
// Load D3.js library dynamically
const loadD3 = () => {
return new Promise((resolve, reject) => {
if (typeof d3 !== 'undefined') {
resolve(d3);
return;
}
const script = document.createElement('script');
script.src = 'https://d3js.org/d3.v7.min.js';
script.onload = () => resolve(window.d3);
script.onerror = reject;
document.head.appendChild(script);
});
};
// Wait for D3 to load, then create chart
loadD3().then(() => {
const {
setGlobalOptions,
events,
env,
getDataFromSearchQuery
} = viz;
setGlobalOptions({
autoEmitRenderCompletedEvent: false,
// Take manual control of XLSX export
autoHandledXLSXDownload: false
});
events.handleXLSXDownloadEvent((payload) => {
console.log("Custom XLSX download payload", payload);
// Here, write your own logic to export the chart built using
// third party library to XLSX file.
return {
isDownloadHandled: true
};
});
const data = getDataFromSearchQuery();
const rawData = data.getData().data;
// Transform to D3 format using array indices (not string keys)
const data1 = rawData.map(row => ({
name: row[0], // Access by index, not row["0"]
value: row[1] // Access by index, not row["1"]
}));
const margin = { top: 20, right: 20, bottom: 30, left: 40 },
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
const svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const x = d3.scaleBand()
.domain(data1.map(d => d.name))
.range([0, width])
.padding(0.2);
const y = d3.scaleLinear()
.domain([0, d3.max(data1, d => d.value)])
.nice()
.range([height, 0]);
// Axes
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
// Bars with animation
const bars = svg.selectAll(".bar")
.data(data1)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => x(d.name))
.attr("y", y(0))
.attr("width", x.bandwidth())
.attr("height", 0)
.attr("fill", "steelblue");
// Render with animation when in normal mode
if (!env.isPrintMode) {
bars
.transition()
.duration(800)
.attr("y", d => y(d.value))
.attr("height", d => height - y(d.value))
.on("end", function (_, i, nodes) {
if (i === nodes.length - 1) {
console.log("✅ Chart fully rendered with animation");
events.emitRenderCompletedEvent();
}
});
} else {
// Disable animations when in print mode
bars
.attr("y", d => y(d.value))
.attr("height", d => height - y(d.value));
events.emitRenderCompletedEvent();
}
}).catch(error => {
console.error("Failed to load D3.js:", error);
});